﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using SKBKontur.Cassandra.Proxy.Selectors;
using SKBKontur.Logging;
using SKBKontur.Logging.Model;

namespace SKBKontur.Cassandra.Proxy.Management
{
    public class StorageClusterManager : IStorageClusterManager
    {
        class PerNodeMultiTryManager: IMultiTryManager
        {
            private readonly FactoryStatistics factoryStatistics;
            private int counter;
            private readonly ILogEventWriter logEventWriter;

            public PerNodeMultiTryManager(FactoryStatistics factoryStatistics, ILogEventWriter logEventWriter)
            {
                this.factoryStatistics = factoryStatistics;
                this.logEventWriter = logEventWriter;
            }

            public void Begin()
            {
                counter = 0;
                factoryStatistics.ReportBegin();
            }

            public void SuccessOp(StorageOperation storageOperation, long elapsedMilliseconds)
            {
                factoryStatistics.ReportSuccess(storageOperation, elapsedMilliseconds);
            }

            public bool CanContinue(StorageOperation storageOperation, long elapsedMilliseconds, Exception e)
            {           
                factoryStatistics.ReportFailure(storageOperation, elapsedMilliseconds, e);
                var returned =  factoryStatistics.CanContinue(storageOperation, ++counter);
                logEventWriter.Create(new LogEvent(e)
                {
                    EventLevel = LogEventLevel.Error,
                    Message = string.Format("Operation {0} is failed on storage, Threadname:{4}, Counter value: {1}, Elapsed {2}ms, IsContinue: {5}", storageOperation, counter, elapsedMilliseconds, e.Message, Thread.CurrentThread.Name, returned)
                });
                return returned;
            }
        }

        class PerClusterMultiTryManager : IMultiTryManager
        {
            private readonly OperationalStatistics operationalStatistics;
            private int counter;
            private readonly ILogEventWriter logEventWriter;

            public PerClusterMultiTryManager(OperationalStatistics operationalStatistics, ILogEventWriter logEventWriter)
            {
                this.operationalStatistics = operationalStatistics;
                this.logEventWriter = logEventWriter;
            }

            public void Begin()
            {
                counter = 0;
                operationalStatistics.ReportBegin();
            }

            public void SuccessOp(StorageOperation storageOperation, long elapsedMilliseconds)
            {
                operationalStatistics.ReportSuccess(storageOperation, elapsedMilliseconds);
                counter++;
            }

            public bool CanContinue(StorageOperation storageOperation, long elapsedMilliseconds, Exception e)
            {
                operationalStatistics.ReportFailure(storageOperation, elapsedMilliseconds, e);
                counter++;
                var returned = (counter < 3);
                logEventWriter.Create(new LogEvent(e)
                {
                    EventLevel = LogEventLevel.Error,
                    Message = string.Format("Operation {0} is failed on node, Threadname:{4}, Counter value: {1}, Elapsed {2}ms, IsContinue: {5}", storageOperation, counter, elapsedMilliseconds, e.Message, Thread.CurrentThread.Name, returned)
                });
                return returned;
            }
        }

        private readonly ILogEventWriter logEventWriter;
        private readonly IStorageFactory[] storageFactories;
        private readonly FactoryStatistics[] factoryStatistics;
        private readonly Dictionary<IStorage, int> factoryByStorage = new Dictionary<IStorage, int>();
        private readonly OperationalStatistics operationalStatistics = new OperationalStatistics();

        public StorageClusterManager(ILogEventWriter logEventWriter, params IStorageFactory[] storageFactories)
        {
            this.logEventWriter = logEventWriter;
            this.storageFactories = storageFactories;
            factoryStatistics = new FactoryStatistics[storageFactories.Length];
            for(int i = 0; i < factoryStatistics.Length; i++)
                factoryStatistics[i] = new FactoryStatistics();
        }

        public IStorage CreateStorage(string keySpace)
        {
            int storageIndex = GetLeastUsedFactory();
            IStorage storage = storageFactories[storageIndex].CreateStorage(keySpace);
            var manager = new PerNodeMultiTryManager(factoryStatistics[storageIndex], logEventWriter);
            var result = new MultiTry(storage, manager);
            lock(factoryByStorage)
                factoryByStorage[result] = storageIndex;
            factoryStatistics[storageIndex].ReportUsage();
            return result;
        }

        private int GetLeastUsedFactory()
        {
            int result = 0;
            int bestUsage = factoryStatistics[result].GetUsage();
            int temp = result + 1;
            for (int i = temp; i < factoryStatistics.Length; i++)
            {
                int currentUsage = factoryStatistics[i].GetUsage();
                if (currentUsage < bestUsage)
                {
                    bestUsage = currentUsage;
                    result = i;
                }
            }
            return result;
        }

        public IMultiTryManager PerClusterManager
        {
            get { return new PerClusterMultiTryManager(operationalStatistics, logEventWriter); }
        }

        private FactoryStatistics GetFactoryStatistics(IStorage storage)
        {
            return factoryStatistics[GetFactoryIndex(storage)];
        }

        private int GetFactoryIndex(IStorage storage)
        {
            lock (factoryByStorage)
                return factoryByStorage[storage];
        }

        public void ReportBroken(IStorage storage)
        {
            GetFactoryStatistics(storage).ReportBroken();
        }

        public bool CanUseFromPool(IStorage storage)
        {
            return true;
        }

        public void ReportUsageFromPool(IStorage storage)
        {
            GetFactoryStatistics(storage).ReportUsageFromPool();
        }

        public void ReportRecycle(IStorage storage)
        {
            GetFactoryStatistics(storage).ReportRecycle();
        }

        public void ReportStatistics()
        {
            logEventWriter.Create(new ClusterStatistics(factoryStatistics, operationalStatistics));
        }
    }
}